/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.transform.wavelet.impl;

import boofcv.alg.transform.wavelet.UtilWavelet;
import boofcv.struct.image.ImageFloat32;
import boofcv.struct.image.ImageSInt32;
import boofcv.struct.wavelet.WlCoef_F32;
import boofcv.struct.wavelet.WlCoef_I32;


/**
 * <p>
 * Standard algorithm for forward and inverse wavelet transform which has been optimized to only
 * process the inner portion of the image by excluding the border.
 * </p>
 *
 * <p>
 * DO NOT MODIFY: This class was automatically generated by {@link GenerateImplWaveletTransformInner}
 * </p>
 *
 * @author Peter Abeles
 */
@SuppressWarnings({"ForLoopReplaceableByForEach"})
public class ImplWaveletTransformInner {

	public static void horizontal( WlCoef_F32 coefficients , ImageFloat32 input , ImageFloat32 output )
	{
		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final float[] alpha = coefficients.scaling;
		final float[] beta = coefficients.wavelet;

		final float dataIn[] = input.data;
		final float dataOut[] = output.data;

		final int width = output.width;
		final int height = input.height;
		final int widthD2 = width/2;
		final int startX = UtilWavelet.borderForwardLower(coefficients);
		final int endOffsetX = input.width - UtilWavelet.borderForwardUpper(coefficients,input.width) - startX;

		for( int y = 0; y < height; y++ ) {

			int indexIn = input.startIndex + input.stride*y + startX;
			int indexOut = output.startIndex + output.stride*y + startX/2;

			int end = indexIn + endOffsetX;

			for( ; indexIn < end; indexIn += 2 ) {

				float scale = 0;
				int index = indexIn+offsetA;
				for( int i = 0; i < alpha.length; i++ ) {
					scale += (dataIn[index++])*alpha[i];
				}

				float wavelet = 0;
				index = indexIn+offsetB;
				for( int i = 0; i < beta.length; i++ ) {
					wavelet += (dataIn[index++])*beta[i];
				}

				dataOut[ indexOut+widthD2] = wavelet;
				dataOut[ indexOut++ ] = scale;
			}
		}
	}

	public static void vertical( WlCoef_F32 coefficients , ImageFloat32 input , ImageFloat32 output )
	{
		final int offsetA = coefficients.offsetScaling*input.stride;
		final int offsetB = coefficients.offsetWavelet*input.stride;
		final float[] alpha = coefficients.scaling;
		final float[] beta = coefficients.wavelet;

		final float dataIn[] = input.data;
		final float dataOut[] = output.data;

		final int width = input.width;
		final int height = output.height;
		final int heightD2 = (height/2)*output.stride;
		final int startY = UtilWavelet.borderForwardLower(coefficients);
		final int endY = input.height - UtilWavelet.borderForwardUpper(coefficients,input.width);

		for( int y = startY; y < endY; y += 2 ) {

			int indexIn = input.startIndex + input.stride*y;
			int indexOut = output.startIndex + output.stride*(y/2);

			for( int x = 0; x < width; x++, indexIn++) {

				float scale = 0;
				int index = indexIn + offsetA;
				for( int i = 0; i < alpha.length; i++ ) {
					scale += (dataIn[index])*alpha[i];
					index += input.stride;
				}

				float wavelet = 0;
				index = indexIn + offsetB;
				for( int i = 0; i < beta.length; i++ ) {
					wavelet += (dataIn[index])*beta[i];
					index += input.stride;
				}

				dataOut[indexOut+heightD2] = wavelet;
				dataOut[indexOut++] = scale;

			}
		}
	}

	public static void horizontalInverse( WlCoef_F32 coefficients , ImageFloat32 input , ImageFloat32 output )
	{
		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final float[] alpha = coefficients.scaling;
		final float[] beta = coefficients.wavelet;

		float []trends = new float[ output.width ];
		float []details = new float[ output.width ];

		final int width = input.width;
		final int height = output.height;
		final int widthD2 = width/2;
		final int lowerBorder = UtilWavelet.borderForwardLower(coefficients);
		final int upperBorder = output.width - UtilWavelet.borderForwardUpper(coefficients,output.width);
		for( int y = 0; y < height; y++ ) {

			// initialize details and trends arrays
			int indexSrc = input.startIndex + y*input.stride+lowerBorder/2;
			for( int x = lowerBorder; x < upperBorder; x += 2 , indexSrc++ ) {
				float a = input.data[ indexSrc ] ;
				float d = input.data[ indexSrc + widthD2 ] ;

				// add the trend
				for( int i = 0; i < 2; i++ )
					trends[i+x+offsetA] = a*alpha[i];

				// add the detail signal
				for( int i = 0; i < 2; i++ )
					details[i+x+offsetB] = d*beta[i];
			}

			for( int i = upperBorder+offsetA; i < upperBorder; i++ )
				trends[i] = 0;
			for( int i = upperBorder+offsetB; i < upperBorder; i++ )
				details[i] = 0;

			// perform the normal inverse transform
			indexSrc = input.startIndex + y*input.stride+lowerBorder/2;
			for( int x = lowerBorder; x < upperBorder; x += 2 , indexSrc++ ) {
				float a = input.data[ indexSrc ] ;
				float d = input.data[ indexSrc + widthD2 ] ;

				// add the trend
				for( int i = 2; i < alpha.length; i++ ) {
					trends[i+x+offsetA] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 2; i < beta.length; i++ ) {
					details[i+x+offsetB] += d*beta[i];
				}
			}

			int indexDst = output.startIndex + y*output.stride + lowerBorder;
			for( int x = lowerBorder; x < upperBorder; x++ ) {
				output.data[ indexDst++ ] = (trends[x] + details[x]);
			}
		}
	}

	public static void verticalInverse( WlCoef_F32 coefficients , ImageFloat32 input , ImageFloat32 output )
	{
		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final float[] alpha = coefficients.scaling;
		final float[] beta = coefficients.wavelet;

		float []trends = new float[ output.height ];
		float []details = new float[ output.height ];

		final int width = output.width;
		final int height = input.height;
		final int heightD2 = (height/2)*input.stride;
		final int lowerBorder = UtilWavelet.borderForwardLower(coefficients);
		final int upperBorder = output.height - UtilWavelet.borderForwardUpper(coefficients,output.height);
		for( int x = 0; x < width; x++) {

			int indexSrc = input.startIndex + (lowerBorder/2)*input.stride + x;
			for( int y = lowerBorder; y < upperBorder; y += 2 , indexSrc += input.stride ) {
				float a = input.data[ indexSrc ] ;
				float d = input.data[ indexSrc + heightD2 ] ;

				// add the trend
				for( int i = 0; i < 2; i++ )
					trends[i+y+offsetA] = a*alpha[i];

				// add the detail signal
				for( int i = 0; i < 2; i++ )
					details[i+y+offsetB] = d*beta[i];
			}

			for( int i = upperBorder+offsetA; i < upperBorder; i++ )
				trends[i] = 0;
			for( int i = upperBorder+offsetB; i < upperBorder; i++ )
				details[i] = 0;

			// perform the normal inverse transform
			indexSrc = input.startIndex + (lowerBorder/2)*input.stride + x;

			for( int y = lowerBorder; y < upperBorder; y += 2 , indexSrc += input.stride ) {
				float a = input.data[indexSrc] ;
				float d = input.data[indexSrc+heightD2] ;

				// add the 'average' signal
				for( int i = 2; i < alpha.length; i++ ) {
					trends[y+offsetA+i] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 2; i < beta.length; i++ ) {
					details[y+offsetB+i] += d*beta[i];
				}
			}

			int indexDst = output.startIndex + x + lowerBorder*output.stride;
			for( int y = lowerBorder; y < upperBorder; y++ , indexDst += output.stride ) {
				output.data[ indexDst ] = (trends[y] + details[y]);
			}
		}
	}

	public static void horizontal( WlCoef_I32 coefficients , ImageSInt32 input , ImageSInt32 output )
	{
		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final int[] alpha = coefficients.scaling;
		final int[] beta = coefficients.wavelet;

		final int dataIn[] = input.data;
		final int dataOut[] = output.data;

		final int width = output.width;
		final int height = input.height;
		final int widthD2 = width/2;
		final int startX = UtilWavelet.borderForwardLower(coefficients);
		final int endOffsetX = input.width - UtilWavelet.borderForwardUpper(coefficients,input.width) - startX;

		for( int y = 0; y < height; y++ ) {

			int indexIn = input.startIndex + input.stride*y + startX;
			int indexOut = output.startIndex + output.stride*y + startX/2;

			int end = indexIn + endOffsetX;

			for( ; indexIn < end; indexIn += 2 ) {

				int scale = 0;
				int index = indexIn+offsetA;
				for( int i = 0; i < alpha.length; i++ ) {
					scale += (dataIn[index++])*alpha[i];
				}

				int wavelet = 0;
				index = indexIn+offsetB;
				for( int i = 0; i < beta.length; i++ ) {
					wavelet += (dataIn[index++])*beta[i];
				}

				scale = 2*scale/coefficients.denominatorScaling;
				wavelet = 2*wavelet/coefficients.denominatorWavelet;

				dataOut[ indexOut+widthD2] = wavelet;
				dataOut[ indexOut++ ] = scale;
			}
		}
	}

	public static void vertical( WlCoef_I32 coefficients , ImageSInt32 input , ImageSInt32 output )
	{
		final int offsetA = coefficients.offsetScaling*input.stride;
		final int offsetB = coefficients.offsetWavelet*input.stride;
		final int[] alpha = coefficients.scaling;
		final int[] beta = coefficients.wavelet;

		final int dataIn[] = input.data;
		final int dataOut[] = output.data;

		final int width = input.width;
		final int height = output.height;
		final int heightD2 = (height/2)*output.stride;
		final int startY = UtilWavelet.borderForwardLower(coefficients);
		final int endY = input.height - UtilWavelet.borderForwardUpper(coefficients,input.width);

		for( int y = startY; y < endY; y += 2 ) {

			int indexIn = input.startIndex + input.stride*y;
			int indexOut = output.startIndex + output.stride*(y/2);

			for( int x = 0; x < width; x++, indexIn++) {

				int scale = 0;
				int index = indexIn + offsetA;
				for( int i = 0; i < alpha.length; i++ ) {
					scale += (dataIn[index])*alpha[i];
					index += input.stride;
				}

				int wavelet = 0;
				index = indexIn + offsetB;
				for( int i = 0; i < beta.length; i++ ) {
					wavelet += (dataIn[index])*beta[i];
					index += input.stride;
				}

				scale = 2*scale/coefficients.denominatorScaling;
				wavelet = 2*wavelet/coefficients.denominatorWavelet;

				dataOut[indexOut+heightD2] = wavelet;
				dataOut[indexOut++] = scale;

			}
		}
	}

	public static void horizontalInverse( WlCoef_I32 coefficients , ImageSInt32 input , ImageSInt32 output )
	{
		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final int[] alpha = coefficients.scaling;
		final int[] beta = coefficients.wavelet;

		int []trends = new int[ output.width ];
		int []details = new int[ output.width ];

		final int width = input.width;
		final int height = output.height;
		final int widthD2 = width/2;
		final int lowerBorder = UtilWavelet.borderForwardLower(coefficients);
		final int upperBorder = output.width - UtilWavelet.borderForwardUpper(coefficients,output.width);
		final int e = coefficients.denominatorScaling*2;
		final int f = coefficients.denominatorWavelet*2;
		final int ef = e*f;
		final int ef2 = ef/2;

		for( int y = 0; y < height; y++ ) {

			// initialize details and trends arrays
			int indexSrc = input.startIndex + y*input.stride+lowerBorder/2;
			for( int x = lowerBorder; x < upperBorder; x += 2 , indexSrc++ ) {
				int a = input.data[ indexSrc ] ;
				int d = input.data[ indexSrc + widthD2 ] ;

				// add the trend
				for( int i = 0; i < 2; i++ )
					trends[i+x+offsetA] = a*alpha[i];

				// add the detail signal
				for( int i = 0; i < 2; i++ )
					details[i+x+offsetB] = d*beta[i];
			}

			for( int i = upperBorder+offsetA; i < upperBorder; i++ )
				trends[i] = 0;
			for( int i = upperBorder+offsetB; i < upperBorder; i++ )
				details[i] = 0;

			// perform the normal inverse transform
			indexSrc = input.startIndex + y*input.stride+lowerBorder/2;
			for( int x = lowerBorder; x < upperBorder; x += 2 , indexSrc++ ) {
				int a = input.data[ indexSrc ] ;
				int d = input.data[ indexSrc + widthD2 ] ;

				// add the trend
				for( int i = 2; i < alpha.length; i++ ) {
					trends[i+x+offsetA] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 2; i < beta.length; i++ ) {
					details[i+x+offsetB] += d*beta[i];
				}
			}

			int indexDst = output.startIndex + y*output.stride + lowerBorder;
			for( int x = lowerBorder; x < upperBorder; x++ ) {
				output.data[ indexDst++ ] = UtilWavelet.round(trends[x]*f + details[x]*e , ef2,ef);
			}
		}
	}

	public static void verticalInverse( WlCoef_I32 coefficients , ImageSInt32 input , ImageSInt32 output )
	{
		final int offsetA = coefficients.offsetScaling;
		final int offsetB = coefficients.offsetWavelet;
		final int[] alpha = coefficients.scaling;
		final int[] beta = coefficients.wavelet;

		int []trends = new int[ output.height ];
		int []details = new int[ output.height ];

		final int width = output.width;
		final int height = input.height;
		final int heightD2 = (height/2)*input.stride;
		final int lowerBorder = UtilWavelet.borderForwardLower(coefficients);
		final int upperBorder = output.height - UtilWavelet.borderForwardUpper(coefficients,output.height);
		final int e = coefficients.denominatorScaling*2;
		final int f = coefficients.denominatorWavelet*2;
		final int ef = e*f;
		final int ef2 = ef/2;

		for( int x = 0; x < width; x++) {

			int indexSrc = input.startIndex + (lowerBorder/2)*input.stride + x;
			for( int y = lowerBorder; y < upperBorder; y += 2 , indexSrc += input.stride ) {
				int a = input.data[ indexSrc ] ;
				int d = input.data[ indexSrc + heightD2 ] ;

				// add the trend
				for( int i = 0; i < 2; i++ )
					trends[i+y+offsetA] = a*alpha[i];

				// add the detail signal
				for( int i = 0; i < 2; i++ )
					details[i+y+offsetB] = d*beta[i];
			}

			for( int i = upperBorder+offsetA; i < upperBorder; i++ )
				trends[i] = 0;
			for( int i = upperBorder+offsetB; i < upperBorder; i++ )
				details[i] = 0;

			// perform the normal inverse transform
			indexSrc = input.startIndex + (lowerBorder/2)*input.stride + x;

			for( int y = lowerBorder; y < upperBorder; y += 2 , indexSrc += input.stride ) {
				int a = input.data[indexSrc] ;
				int d = input.data[indexSrc+heightD2] ;

				// add the 'average' signal
				for( int i = 2; i < alpha.length; i++ ) {
					trends[y+offsetA+i] += a*alpha[i];
				}

				// add the detail signal
				for( int i = 2; i < beta.length; i++ ) {
					details[y+offsetB+i] += d*beta[i];
				}
			}

			int indexDst = output.startIndex + x + lowerBorder*output.stride;
			for( int y = lowerBorder; y < upperBorder; y++ , indexDst += output.stride ) {
				output.data[ indexDst ] = UtilWavelet.round(trends[y]*f + details[y]*e , ef2 , ef);
			}
		}
	}


}
